// (c) S.A. Velastin 2007
/*
        A program that uses the REASON Architecture framework to demonstrate a video server
        Syntax
                VideoServer factory_name object_name type video_file {object_name type video_file}
                
                factory_name:   Name of the server factory that will serve these objects
                object_name:    the name for the object associated with a video stream
                type:           channel, pool or signal determines what type of IDA to use (recommended: signal)
                video_file:     Name of the video file associated with the video stream
                
         In this implementation a single thread serves all streams (not very elegant but this is only a demo!)
*/
#include <tao/corba.h>
#include <iostream>
#include <fstream>
#include <string>
#include <list>

// Corba stuff
#include "orbsvcs/CosNamingC.h"
#include "CommunicationC.h"

// Opencv stuff
#include <cv.h>
#include <highgui.h>

#include "common.h"

using namespace std;

//--------------------------------------------------------------------------------------------
// Gets a reference to the Name Service
CosNaming::NamingContext_var GetNamingContext (CORBA::ORB_var orb) {
  CosNaming::NamingContext_var naming_context = NULL;
  cout << "Trying to locate the Naming Service\n";
  try {
    CORBA::Object_var naming_context_object = orb->resolve_initial_references ("NameService");
    naming_context = CosNaming::NamingContext::_narrow (naming_context_object.in ());
    if (CORBA::is_nil(naming_context))
      cout << "Could not resolve Name Service\n";
    else
      cout << "Got Name Service\n";
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting Name Service: " << e << endl;
  }
  return naming_context;
}

//--------------------------------------------------------------------------------------------
// Gets a reference to an IDA Factory via Naming Service
TIDAFactory_var GetIDAFactory (char *FactoryName, CosNaming::NamingContext_var naming_context) {
  TIDAFactory_var Factory = NULL;
  cout << "Trying to get reference to factory object '" << FactoryName << "'\n";
  // Create and initialise the name sequence
  CosNaming::Name name (1);
  name.length(1);
  // Initialise the name
  name[0].id = CORBA::string_dup (FactoryName);

  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) 
      cerr << "Unable to resolve name\n";
    else {
        // Get a reference to the Factory object
      Factory = TIDAFactory::_narrow(obj);
      if (CORBA::is_nil(Factory))
        cerr << "Unable to get Factory reference\n";
      return Factory;
    }
  }
  catch (CORBA::Exception& e) {
    cerr << "Exception when getting factory: " << e << endl;
  }
  return Factory;
}

//--------------------------------------------------------------------------------------------
// Gets a reference to an object by getting it from the Name Service
TIDA_var GetIDAReference (char *objname, CosNaming::NamingContext_var naming_context) {
  TIDA_var result=NULL;
  CosNaming::Name name (1);
  name.length(1);
  name[0].id = CORBA::string_dup (objname);
  cout << "Client looking for IDA name '" << objname << "' in name service\n";
  try {
    CORBA::Object_var obj = naming_context->resolve (name);
    if (CORBA::is_nil(obj)) {
    cerr << "Unable to resolve name\n";
    return NULL;
    }
    // The name was resolved, try to use the object
    result = TIDA::_narrow (obj);
    if (CORBA::is_nil(result))
      cerr << "Unable to get the Factory's object reference\n";
  }
  catch (CORBA::Exception &e) {
    cerr << "Exception when getting IDA object reference: " << e << endl;
  }
  return result;  
}

//------------------------------------------------------------------------------
// Fills up an IplImage from a TImage
// Assumed that Image owns its pixel data so we create a new array to be owned by CVImage
// NB: Caller has responsibility for freeing memory in CVImage
bool FillOpenCVData (IplImage *CVImage, const TImage *Image)
{ if (Image->imageSize != Image->ImagePixels.length()) {
  cerr << "FillOpenCVData: inconsistent image size " << Image->imageSize << " and length " 
      << Image->ImagePixels.length() << "\n";
  return false;
}
  
  CVImage->nSize = sizeof(IplImage);
  CVImage->ID = 0;                      // version (=0)
  CVImage->nChannels = Image->nChannels;// Most of OpenCV functions support 1,2,3 or 4 channels 
  CVImage->alphaChannel = 0;            // ignored by OpenCV 
  CVImage->depth = Image->depth;        // pixel depth in bits: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U,
                                        //IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F are supported
  //    char colorModel[4];           ignored by OpenCV 
  //    char channelSeq[4];           ditto 
  CVImage->dataOrder = 0;               // 0 - interleaved color channels, 1 - separate color channels.
                                        // cvCreateImage can only create interleaved images 
  CVImage->origin = Image->origin;      // 0 - top-left origin,
                                        // 1 - bottom-left origin (Windows bitmaps style) 
  // int  align;                           Alignment of image rows (4 or 8).
                                        // OpenCV ignores it and uses widthStep instead 
  CVImage->width = Image->width;        // image width in pixels 
  CVImage->height = Image->height;      // image height in pixels 
  CVImage->roi = NULL;                  // image ROI. when it is not NULL, this specifies image region to process 
  CVImage->maskROI = NULL;              // must be NULL in OpenCV 
  CVImage->imageId = NULL;              // ditto
  CVImage->tileInfo = NULL;             // ditto
  CVImage->imageSize = Image->imageSize;// image data size in bytes
                                        // (=image->height*image->widthStep
                                        //  in case of interleaved data)
  CVImage->widthStep = Image->widthStep;// size of aligned image row in bytes
  //       int  BorderMode[4];           border completion mode, ignored by OpenCV 
  //       int  BorderConst[4];          ditto
  char *Pixels = new (nothrow) char[Image->imageSize];
  if (!Pixels) {
    cerr << "FillOpenCVData: could not create pixels array\n";
    return false;
  }
  memcpy (Pixels, Image->ImagePixels.get_buffer(), Image->imageSize);
  
  CVImage->imageData = Pixels;          // pointer to aligned image data
  CVImage->imageDataOrigin = Pixels;    // pointer to a very origin of image data
                                        // (not necessarily aligned) -
                                        // it is needed for correct image deallocation
  // note that the caller is now responsible for releasing Pixels
  return true;
}

//----------------------------------------------------------------------------------
// Fills a TImage from IplImage
// A new array of pixels is created that is then managed by Image
bool FillImageData (TImage *Image, const IplImage *CVImage)
{ Image->nChannels = CVImage->nChannels;
  Image->depth = CVImage->depth;
  Image->origin = CVImage->origin;
  Image->width = CVImage->width;
  Image->height = CVImage->height;
  Image->imageSize = (CORBA::Long) CVImage->imageSize;
  Image->widthStep = CVImage->widthStep;
  // Now copy the pixel data by creating memory and copying
  CORBA::Octet *ImageData;
  ImageData = new (nothrow) CORBA::Octet[CVImage->imageSize];
  if (!ImageData) {
    cerr << "FillImageData failed to create temporary memory\n";
    return false;
  }
  memcpy (ImageData, CVImage->imageDataOrigin, CVImage->imageSize);  // copy all pixels
  
  // Important: Image now manages ImageData, so do NOT delete it
  // replace() should deal with freeing previous data
  Image->ImagePixels.replace (CVImage->imageSize, CVImage->imageSize, ImageData);
  cout << "Pixel data copied, size: " << CVImage->imageSize << " " << Image->imageSize << 
      " " << Image->ImagePixels.length() << "\n";
  return true;
}

// Possible exit codes
#define EXIT_NORMAL             0
#define EXIT_WRONGPARAMETERS    1
#define EXIT_NONAMINGSERVICE    2
#define EXIT_NOFACTORY          3
#define EXIT_NOIMAGEMEMORY      4
#define EXIT_NOIMAGEFILE        5
#define EXIT_NOVIDEOSERVER      6

// TODO: Check memory allocation to trap orphan memory

//--------------------------------------------------------------------------------------------
int main(int argc, char* argv[]) { 

  try { 
    CosNaming::NamingContext_var naming_context;        // Reference to Name Service
    TIDAFactory_var     Factory;                        // Reference to an IDA Factory
    TIDA_var            VideoServer;                    // Reference to the video server
    bool                NoMore=false;                   // Signals that there is no more video
    
        
    // Process & remove any CORBA arguments in the command line
    CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);
    // Check command line arguments.
    if (argc < 3) { 
      cerr << "Usage: VideoClient factory_name object_name\n";
      exit (EXIT_WRONGPARAMETERS); }
    
    // Get a reference to the naming service
    naming_context = GetNamingContext (orb);
    if (CORBA::is_nil(naming_context))
      exit (EXIT_NONAMINGSERVICE);
    
    // Get a reference to the factory object
    cout << "Client trying to check if factory '" << argv[1] << "' exists\n";
    Factory = GetIDAFactory (argv[1], naming_context);
    if (CORBA::is_nil (Factory))  
      exit (EXIT_NOFACTORY);
    
    // Now we see if we can get a reference to the object
    VideoServer = GetIDAReference (argv[2], naming_context);
    if (CORBA::is_nil (VideoServer))
      exit (EXIT_NOVIDEOSERVER);
    cvNamedWindow("Video", CV_WINDOW_AUTOSIZE);         // create a window for the video
    
    cout << "Client ready to receive, press return to continue "; getchar();

    // Loop until there is no more video
    // TODO: server needs to give an indication that there is no more video
    do {
      CORBA::Any ReceivedData;
      if (VideoServer->GetData (ReceivedData, 100000)) { // try to get data from the video server, 100ms timeout
        // TODO: decode server's indication that there is no more video and set NoMore
        cout << "We have data\n";
        TImage *PNewImage;                      // a pointer to access the data
        if (ReceivedData >>= PNewImage) {
            // NB: Remember that PTImage is only a handle, the data is still owned by ReadImage
          IplImage ImageData;                  // opencv image (Note that this is not a pointer!)
          cout << "Data is: " 
              << "W " << PNewImage->width << " H " << PNewImage->height << " S "
              << PNewImage->imageSize << " WS " <<
              PNewImage->widthStep << " L " << PNewImage->ImagePixels.length() << "\n";
          if (FillOpenCVData (&ImageData, PNewImage)) {
            cvShowImage ("Video", &ImageData);
            cvWaitKey (500);
          }
        }
        else
          cout << "Data read was from the incorrect type!\n";
      }
      else
        cout << ".";
    } while (!NoMore);
    // TODO: undo all dynamic memory creation
  }
  catch (CORBA::Exception& e) {
    cerr << "CORBA Exception: " << e << endl;
  }
  cout << "Exiting ...\n";
  return EXIT_NORMAL; 
}
